﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Threading;
using System.IO;
using Newtonsoft.Json;
using OpenHardwareMonitor.Hardware;

namespace RemoteHardwareMonitor
{
    /// <summary>
    /// Singleton implementation as OpenHardwareMonitor doesn't allow multiple accesses. 
    /// </summary>
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] 
    public class RemoteHardwareMonitorService : IHardwareMonSrvce
    {
        XmlDocument doc;
        HardwareMonitorProvider hardwareMonitor;
        TimerCallback callBack;
        Timer pollTimer;
        IHardware[] GPUs;
        IHardware[] CPUs;
        SensorsToXML XmlMapper;

        public RemoteHardwareMonitorService()
        {
            hardwareMonitor = new HardwareMonitorProvider();

            hardwareMonitor.OnSuccessfulInitialPoll += new HardwareMonitorProvider.SuccessfulinitialPoll(hardwareMonitor_OnSuccessfulInitialPoll);

            hardwareMonitor.InitialPoll();

        }


        private void BypassCrossDomain()
        {
            if (WebOperationContext.Current.OutgoingResponse.Headers.Count == 0)
            {
                WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
            }
            
        }

        private float clipTo100(float value)
    {
        return value < 100 ? value : 100;
    }

       //http://127.0.0.1:8080/index/GetCPUName?cpu=0
        public string GetCPUName(int cpu)
        {
            BypassCrossDomain();

            if (cpu < GetCPUCount() - 1) throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            XmlNode dataNode = doc.SelectSingleNode("//hardware/CPUs/CPU[@index=" + cpu + "]/@name");
            if (dataNode == null)
            {
                throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            }
            return dataNode.InnerText;
        }

        //http://127.0.0.1:8080/index/GetCPUCount
        public int GetCPUCount()
        {
            BypassCrossDomain();

            XmlNode dataNode = doc.SelectSingleNode("//hardware/CPUs/@count");
            if (dataNode == null)
            {
                throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            }
            return int.Parse(dataNode.InnerText);
        }

        public string GetCPUCountJ()
        {

            BypassCrossDomain();

            StringBuilder sb = new StringBuilder();
            StringWriter sw = new StringWriter(sb);
            using (JsonTextWriter jW = new JsonTextWriter(sw))
            {
                jW.WriteStartObject();
                jW.WritePropertyName("Count");
                jW.WriteValue(GetCPUCount());
                jW.WriteEndObject();
                jW.Close();
            }
            return sb.ToString();
        }

        //http://127.0.0.1:8080/index/GetCPULoad?cpu=0
        public float GetCPULoad(int cpu)
        {
            BypassCrossDomain();

            if (cpu < GetCPUCount() - 1) throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            XmlNode dataNode = doc.SelectSingleNode("//hardware/CPUs/CPU[@index=" + cpu + "]/sensors/CPUTotalLoad");
            if (dataNode == null)
            {
                throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            }
            return clipTo100(float.Parse(dataNode.InnerText));
        }

        //http://127.0.0.1:8080/index/GetCoreCount?cpu=0
        public int GetCoreCount(int cpu)
        {
            try
            {
                if (cpu < GetCPUCount() - 1) throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
                XmlNode dataNode = doc.SelectSingleNode("//hardware/CPUs/CPU[@index=" + cpu + "]/sensors");
                /*
                 * for each core, one load sensor, one temp sensor plus a CPU total load sensor.
                 */
                return (dataNode.ChildNodes.Count -1)/2;
            }
            catch(Exception ex)
            {
#if DEBUG
                Console.WriteLine(ex.ToString());
                return 0;
#endif
            }
            
        }

        public string GetCoreCountJ(int cpu)
        {

            BypassCrossDomain();

            StringBuilder sb = new StringBuilder();
            StringWriter sw = new StringWriter(sb);
            using (JsonTextWriter jW = new JsonTextWriter(sw))
            {
                jW.WriteStartObject();
                jW.WritePropertyName("Count");
                jW.WriteValue(GetCoreCount(cpu));
                jW.WriteEndObject();
                jW.Close();
            }
            return sb.ToString();
        }

        //http://127.0.0.1:8080/index/GetCoreLoad?cpu=0&core=0
        public float GetCoreLoad(int cpu, int core)
        {
            BypassCrossDomain();

            // HardwareMonitor cpu core numbering is one based whereas CPU numbering is zero based. Standardise by adding one to input parameter.
            if (cpu < GetCPUCount() - 1) throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            core++;
            XmlNode dataNode = doc.SelectSingleNode("//hardware/CPUs/CPU[@index=" + cpu + "]/sensors/CPUCore" + core + "Load/Current");
            if (dataNode == null)
            {
                throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            }
            return clipTo100(float.Parse(dataNode.InnerText));
        }


        //http://127.0.0.1:8080/index/GetCoreLoadJ?cpu=0&core=0
        public string GetCoreLoadJ(int cpu, int core)
        {
            BypassCrossDomain();
           
            StringBuilder sb = new StringBuilder();
            StringWriter sw = new StringWriter(sb);
            using (JsonTextWriter jW = new JsonTextWriter(sw))
            {
                jW.WriteStartObject();
                jW.WritePropertyName("Load");
                jW.WriteValue(GetCoreLoad(cpu, core));
                jW.WriteEndObject();

                jW.Close();
            }
            return sb.ToString();
        }

        //http://127.0.0.1:8080/index/GetAllCoreDataJ?cpu=0
        public string GetAllCoreDataJ(int cpu)
        {
            BypassCrossDomain();
            if (cpu < GetCPUCount() - 1) throw new WebFaultException(System.Net.HttpStatusCode.NotFound);

            StringBuilder sb = new StringBuilder();
            StringWriter sw = new StringWriter(sb);
            int coreCount = GetCoreCount(cpu);
            using (JsonTextWriter jW = new JsonTextWriter(sw))
            {
                jW.WriteStartObject();
                jW.WritePropertyName("Name");
                jW.WriteValue(GetCPUName(cpu));
                jW.WritePropertyName("Cores");
                jW.WriteStartArray();
                for (int i = 1; i <= coreCount; i++)
                {
                    jW.WriteStartObject();
                    jW.WritePropertyName("id");
                    jW.WriteValue((i - 1).ToString());
                    jW.WritePropertyName("LoadCurrent");
                    jW.WriteValue(GetCoreLoad(cpu, i-1));
                    jW.WritePropertyName("LoadMax");
                    jW.WriteValue(0);// not implemented
                    jW.WritePropertyName("TemperatureCurrent");
                    jW.WriteValue(GetCoreTemperature(cpu, i - 1));
                    jW.WritePropertyName("TemperatureMax");
                    jW.WriteValue(0);// not implemented
                    jW.WriteEndObject();
                }
                
                jW.Close();
            }
            return sb.ToString();
        }

        public float GetCoreTemperature(int cpu, int core)
        {
            BypassCrossDomain();

            // HardwareMonitor cpu core numbering is one based whereas CPU numbering is zero based. Standardise by adding one to input parameter.
            if (cpu < GetCPUCount() - 1) throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            core++;
            XmlNode dataNode = doc.SelectSingleNode("//hardware/CPUs/CPU[@index=" + cpu + "]/sensors/CPUCore" + core + "Temperature");
            if (dataNode == null)
            {
                throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            }
            return float.Parse(dataNode.InnerText)/100;
        }

        //http://127.0.0.1:8080/index/GetGPUName?gpu=0
        public string GetGPUName(int gpu)
        {
            BypassCrossDomain();

            if (gpu < GetGPUCount() - 1) throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            XmlNode dataNode = doc.SelectSingleNode("//hardware/GPUs/GPU[@index=" + gpu + "]/@name");
            if (dataNode == null)
            {
                throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            }
            return dataNode.InnerText;
        }

        //http://127.0.0.1:8080/index/GetGPUCount
        public int GetGPUCount()
        {

            BypassCrossDomain();

            XmlNode dataNode = doc.SelectSingleNode("//hardware/GPUs/@count");
            if (dataNode == null)
            {
                throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            }
            return int.Parse(dataNode.InnerText);
        }

        public string GetGPUCountJ()
        {

            BypassCrossDomain();

            StringBuilder sb = new StringBuilder();
            StringWriter sw = new StringWriter(sb);
            using (JsonTextWriter jW = new JsonTextWriter(sw))
            {
                jW.WriteStartObject();
                jW.WritePropertyName("Count");
                jW.WriteValue(GetGPUCount());
                jW.WriteEndObject();
                jW.Close();
            }
            return sb.ToString();
        }

        //http://127.0.0.1:8080/index/GetGPULoad
        public float GetGPULoad(int gpu)
        {
            BypassCrossDomain();

            if (gpu < GetGPUCount() - 1) throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            XmlNode dataNode = doc.SelectSingleNode("//hardware/GPUs/GPU[@index=" + gpu + "]/sensors/GPUCoreLoad");
            if (dataNode == null)
            {
                throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            }
            return clipTo100( float.Parse(dataNode.InnerText));
        }

        public double GetGPULoadMax(int gpu)
        {

            BypassCrossDomain();

            if (gpu < GetGPUCount() - 1) throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            throw new WebFaultException(System.Net.HttpStatusCode.NotImplemented);
        }

        public float GetGPUTemperature(int gpu)
        {
            BypassCrossDomain();

            if (gpu < GetGPUCount() - 1) throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            XmlNode dataNode = doc.SelectSingleNode("//hardware/GPUs/GPU[@index=" + gpu + "]/sensors/GPUCoreTemperature");
            if (dataNode == null)
            {
                throw new WebFaultException(System.Net.HttpStatusCode.NotFound);
            }
            return float.Parse(dataNode.InnerText)/100;
        }

        //http://127.0.0.1:8080/index/GetAllGPUDataJ?gpu=0
        public string GetAllGPUDataJ(int gpu)
        {
            BypassCrossDomain();

            if (gpu < GetGPUCount() - 1) throw new WebFaultException(System.Net.HttpStatusCode.NotFound);

            StringBuilder sb = new StringBuilder();
            StringWriter sw = new StringWriter(sb);
            using (JsonTextWriter jW = new JsonTextWriter(sw))
            {
                jW.WriteStartObject();
                jW.WritePropertyName("Name");
                jW.WriteValue(GetGPUName(gpu));
                jW.WritePropertyName("LoadCurrent");
                jW.WriteValue(GetGPULoad(gpu));
                jW.WritePropertyName("LoadMax");
                jW.WriteValue(0);// not implemented
                jW.WritePropertyName("TemperatureCurrent");
                jW.WriteValue(GetGPUTemperature(gpu));
                jW.WritePropertyName("TemperatureMax");
                jW.WriteValue(0);// not implemented
                jW.WriteEndObject();
                jW.Close();
            }
            return sb.ToString();
        }

        /// <summary>
        /// set up sensor map and xml map and initiate continous polling
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void hardwareMonitor_OnSuccessfulInitialPoll(object sender, EventArgs e)
        {
            // first hardware poll
            callBack = hardwareMonitor.Poll;
            pollTimer = new Timer(callBack, null, 1000, System.Threading.Timeout.Infinite);

            GPUs = hardwareMonitor.GPUs;
            CPUs = hardwareMonitor.CPUs;

            SensorMap sMap = new SensorMap(GPUs, CPUs);
            XmlMapper = new SensorsToXML(sMap);
            // don't want to act on standard poll initial poll is successful;
            hardwareMonitor.OnSuccessfulHardwarePoll += new HardwareMonitorProvider.SuccessfulHardwarePoll(hardwareMonitor_OnSuccessfulHardwarePoll);

        }

        /// <summary>
        /// Update xml data map and re-start timer to poll again.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void hardwareMonitor_OnSuccessfulHardwarePoll(object sender, EventArgs e)
        {
            GPUs = hardwareMonitor.GPUs;
            CPUs = hardwareMonitor.CPUs;
            doc = XmlMapper.MapToDocument(GPUs, CPUs);
            // can poll again now:
            pollTimer = new Timer(callBack, null, 1000, System.Threading.Timeout.Infinite);

        }


    }
}

